﻿using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace LabColorPicker {

    public partial class TwoDimensionalSliceControl: UserControl, INotifyPropertyChanged {

        //
        // Interface
        //

        // Constructor

        public TwoDimensionalSliceControl( ) {
            InitializeComponent( );
        }

        // Properties

        public new double Height {
            get { return base.Height; }
            set {
                lock ( _lockObject ) {
                    base.Height = value;
                    Slice.Height = base.Height - Slice.Margin.Top - Slice.Margin.Bottom;
                    SetMarkerPositionFromValue( );
                }
                _OnPropertyChanged( "Height" );
            }
        }

        public new double Width {
            get { return base.Width; }
            set {
                lock ( _lockObject ) {
                    base.Width = value;
                    Slice.Width = base.Width - Slice.Margin.Left - Slice.Margin.Right;
                    SetMarkerPositionFromValue( );
                }
                _OnPropertyChanged( "Width" );
            }
        }

        public ImageSource Source {
            get { return Slice.Source; }
            set {
                Slice.Source = value;
                _OnPropertyChanged( "Source" );
            }
        }

        // Horizontal properties

        private double _horizontalMaximum = 255d;
        public double HorizontalMaximum {
            get { return _horizontalMaximum; }
            set {
                lock ( _lockObject ) {
                    _horizontalMaximum = value;
                    _horizontalRange = _horizontalMaximum - _horizontalMinimum;
                }
                _OnPropertyChanged( "HorizontalMaximum" );
                _OnPropertyChanged( "HorizontalRange" );
            }
        }

        private double _horizontalMinimum = 0d;
        public double HorizontalMinimum {
            get { return _horizontalMinimum; }
            set {
                lock ( _lockObject ) {
                    _horizontalMinimum = value;
                    _horizontalRange = _horizontalMaximum - _horizontalMinimum;
                }
                _OnPropertyChanged( "HorizontalMinimum" );
                _OnPropertyChanged( "HorizontalRange" );
            }
        }

        private double _horizontalRange = 255d;
        public double HorizontalRange {
            get { return _horizontalRange; }
        }

        private double _horizontalValue;
        public double HorizontalValue {
            get { return _horizontalValue; }
            set {
                lock ( _lockObject ) {
                    _horizontalValue = value;
                    SetMarkerPositionFromValue( );
                }
                _OnPropertyChanged( "HorizontalValue" );
            }
        }

        // Vertical properties

        private double _verticalMaximum = 255d;
        public double VerticalMaximum {
            get { return _verticalMaximum; }
            set {
                lock ( _lockObject ) {
                    _verticalMaximum = value;
                    _verticalRange = _verticalMaximum - _verticalMinimum;
                }
                _OnPropertyChanged( "VerticalMaximum" );
                _OnPropertyChanged( "VerticalRange" );
            }
        }

        private double _verticalMinimum = 0d;
        public double VerticalMinimum {
            get { return _verticalMinimum; }
            set {
                lock ( _lockObject ) {
                    _verticalMinimum = value;
                    _verticalRange = _verticalMaximum - _verticalMinimum;
                }
                _OnPropertyChanged( "VerticalMinimum" );
                _OnPropertyChanged( "VerticalRange" );
            }
        }

        private double _verticalRange = 255d;
        public double VerticalRange {
            get { return _verticalRange; }
        }

        private double _verticalValue;
        public double VerticalValue {
            get { return _verticalValue; }
            set {
                lock ( _lockObject ) {
                    _verticalValue = value;
                    SetMarkerPositionFromValue( );
                }
                _OnPropertyChanged( "VerticalValue" );
            }
        }

        // Methods

        public void SetMinimaAndMaxima( double hMinimum, double hMaximum, double vMinimum, double vMaximum ) {
            lock ( _lockObject ) {
                _horizontalMinimum = hMinimum;
                _horizontalMaximum = hMaximum;
                _horizontalRange = _horizontalMaximum - _horizontalMinimum;

                _verticalMinimum = vMinimum;
                _verticalMaximum = vMaximum;
                _verticalRange = _verticalMaximum - _verticalMinimum;

                SetMarkerPositionFromValue( );
            }

            _OnPropertyChanged( "HorizontalMinimum" );
            _OnPropertyChanged( "HorizontalMaximum" );
            _OnPropertyChanged( "HorizontalRange" );

            _OnPropertyChanged( "VerticalMinimum" );
            _OnPropertyChanged( "VerticalMaximum" );
            _OnPropertyChanged( "VerticalRange" );
        }

        public void SetMinimaMaximaAndValues( double hMinimum, double hMaximum, double hValue, double vMinimum, double vMaximum, double vValue ) {
            lock ( _lockObject ) {
                _horizontalMinimum = hMinimum;
                _horizontalMaximum = hMaximum;
                _horizontalRange = _horizontalMaximum - _horizontalMinimum;
                _horizontalValue = hValue;

                _verticalMinimum = vMinimum;
                _verticalMaximum = vMaximum;
                _verticalRange = _verticalMaximum - _verticalMinimum;
                _verticalValue = vValue;

                SetMarkerPositionFromValue( );
            }

            _OnPropertyChanged( "HorizontalMinimum" );
            _OnPropertyChanged( "HorizontalMaximum" );
            _OnPropertyChanged( "HorizontalRange" );
            _OnPropertyChanged( "HorizontalValue" );

            _OnPropertyChanged( "VerticalMinimum" );
            _OnPropertyChanged( "VerticalMaximum" );
            _OnPropertyChanged( "VerticalRange" );
            _OnPropertyChanged( "VerticalValue" );
        }

        public void SetHorizontalMinimumAndMaximum( double minimum, double maximum ) {
            lock ( _lockObject ) {
                _horizontalMinimum = minimum;
                _horizontalMaximum = maximum;
                _horizontalRange = _horizontalMaximum - _horizontalMinimum;

                SetMarkerPositionFromValue( );
            }

            _OnPropertyChanged( "HorizontalMinimum" );
            _OnPropertyChanged( "HorizontalMaximum" );
            _OnPropertyChanged( "HorizontalRange" );
        }

        public void SetHorizontalMinimumMaximumAndValue( double minimum, double maximum, double value ) {
            lock ( _lockObject ) {
                _horizontalMinimum = minimum;
                _horizontalMaximum = maximum;
                _horizontalRange = _horizontalMaximum - _horizontalMinimum;
                _horizontalValue = value;

                SetMarkerPositionFromValue( );
            }

            _OnPropertyChanged( "HorizontalMinimum" );
            _OnPropertyChanged( "HorizontalMaximum" );
            _OnPropertyChanged( "HorizontalRange" );
            _OnPropertyChanged( "HorizontalValue" );
        }

        public void SetVerticalMinimumAndMaximum( double minimum, double maximum ) {
            lock ( _lockObject ) {
                _verticalMinimum = minimum;
                _verticalMaximum = maximum;
                _verticalRange = _verticalMaximum - _verticalMinimum;

                SetMarkerPositionFromValue( );
            }

            _OnPropertyChanged( "VerticalMinimum" );
            _OnPropertyChanged( "VerticalMaximum" );
            _OnPropertyChanged( "VerticalRange" );
        }

        public void SetVerticalMinimumMaximumAndValue( double minimum, double maximum, double value ) {
            lock ( _lockObject ) {
                _verticalMinimum = minimum;
                _verticalMaximum = maximum;
                _verticalRange = _verticalMaximum - _verticalMinimum;
                _verticalValue = value;

                SetMarkerPositionFromValue( );
            }

            _OnPropertyChanged( "VerticalMinimum" );
            _OnPropertyChanged( "VerticalMaximum" );
            _OnPropertyChanged( "VerticalRange" );
            _OnPropertyChanged( "VerticalValue" );
        }

        // Events

        public event EventHandler ValueChanged;
        private void _OnValueChanged( ) {
            var handlers = ValueChanged;
            if ( null != handlers ) {
                handlers( this, EventArgs.Empty );
            }
        }

        //
        // Implementation
        //

        private readonly object _lockObject = new object( );

        protected Point MarkerPosition = new Point( );

        protected void SetValueFromMarkerPosition( Point pt ) {
            if ( !IsLoaded ) {
                return;
            }

            lock ( _lockObject ) {
                var pixelXRange = Slice.ActualWidth - 1d;
                var pixelYRange = Slice.ActualHeight - 1d;

                var newValue = new Point(
                    MathX.Clamp( pt.X, pixelXRange ),
                    MathX.Clamp( pt.Y, pixelYRange )
                );

                MarkerPosition.X = newValue.X;
                MarkerPosition.Y = newValue.Y;

                _horizontalValue = MathX.Translate( MathX.Scale( newValue.X, pixelXRange, _horizontalRange ), _horizontalMinimum );
                _verticalValue = MathX.Translate( MathX.Scale( pixelYRange - newValue.Y, pixelYRange, _verticalRange ), _verticalMinimum );
            }

            UpdateHorizontalMarkers( );
            UpdateVerticalMarkers( );

            _OnValueChanged( );
            _OnPropertyChanged( "HorizontalValue" );
            _OnPropertyChanged( "VerticalValue" );
        }

        protected void SetMarkerPositionFromValue( ) {
            if ( !IsLoaded ) {
                return;
            }

            lock ( _lockObject ) {
                var pixelXRange = Slice.ActualWidth - 1d;
                var pixelYRange = Slice.ActualHeight - 1d;

                MarkerPosition.X = MathX.Clamp( MathX.Scale( MathX.Translate( _horizontalValue, -_horizontalMinimum ), _horizontalRange, pixelXRange ), pixelXRange );
                MarkerPosition.Y = MathX.Clamp( MathX.Scale( MathX.Translate( _verticalValue, -_verticalMinimum ), _verticalRange, pixelYRange ), pixelYRange );
            }

            UpdateHorizontalMarkers( );
            UpdateVerticalMarkers( );
        }

        protected void UpdateHorizontalMarkers( ) {
            LeftArrow.MoveTo( 0d, MarkerPosition.Y + 1d );
            RightArrow.MoveTo( Slice.Margin.Left + Slice.ActualWidth, MarkerPosition.Y + 1d );
        }

        protected void UpdateVerticalMarkers( ) {
            TopArrow.MoveTo( MarkerPosition.X + 1d, 0d );
            BottomArrow.MoveTo( MarkerPosition.X + 1d, Slice.Margin.Top + Slice.ActualHeight );
        }

        // Event handlers

        private void UserControl_Loaded( object sender, RoutedEventArgs e ) {
            SetMarkerPositionFromValue( );
        }

        private void Slice_LeftButtonDown( object sender, MouseEventArgs ev ) {
            ev.Handled = true;

            var source = (UIElement) sender;
            source.MouseLeftButtonUp += Slice_LeftButtonUp;
            source.MouseMove += Slice_MouseMove;
            source.CaptureMouse( );

            SetValueFromMarkerPosition( ev.GetPosition( Slice ) );
        }

        private void Slice_LeftButtonUp( object sender, MouseEventArgs ev ) {
            ev.Handled = true;

            var source = (UIElement) sender;
            source.ReleaseMouseCapture( );
            source.MouseLeftButtonUp -= Slice_LeftButtonUp;
            source.MouseMove -= Slice_MouseMove;

            SetValueFromMarkerPosition( ev.GetPosition( Slice ) );
        }

        private void Slice_MouseMove( object sender, MouseEventArgs ev ) {
            if ( !Slice.IsMouseCaptured || Keyboard.Modifiers != ModifierKeys.None ) {
                return;
            }
            ev.Handled = true;

            SetValueFromMarkerPosition( ev.GetPosition( Slice ) );
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        private void _OnPropertyChanged( string propertyName ) {
            var handlers = PropertyChanged;
            if ( null != handlers ) {
                handlers( this, new PropertyChangedEventArgs( propertyName ) );
            }
        }

        #endregion

    }

}
